{
 "cells": [
  {
   "cell_type": "raw",
   "metadata": {
    "vscode": {
     "languageId": "raw"
    }
   },
   "source": [
    "---\n",
    "sidebar_position: 1\n",
    "keywords: [conversationchain]\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Build a Chatbot\n",
    "\n",
    "\n",
    ":::info Prerequisites\n",
    "\n",
    "This guide assumes familiarity with the following concepts:\n",
    "\n",
    "- [Chat Models](/docs/concepts/chat_models)\n",
    "- [Prompt Templates](/docs/concepts/prompt_templates)\n",
    "- [Chat History](/docs/concepts/chat_history)\n",
    "\n",
    "This guide requires `langgraph >= 0.2.28`.\n",
    "\n",
    ":::\n",
    "\n",
    "\n",
    "```{=mdx}\n",
    "\n",
    ":::note\n",
    "\n",
    "This tutorial previously built a chatbot using [RunnableWithMessageHistory](https://api.js.langchain.com/classes/_langchain_core.runnables.RunnableWithMessageHistory.html). You can access this version of the tutorial in the [v0.2 docs](https://js.langchain.com/v0.2/docs/tutorials/chatbot/).\n",
    "\n",
    "The LangGraph implementation offers a number of advantages over `RunnableWithMessageHistory`, including the ability to persist arbitrary components of an application's state (instead of only messages).\n",
    "\n",
    ":::\n",
    "\n",
    "```\n",
    "\n",
    "## Overview\n",
    "\n",
    "We'll go over an example of how to design and implement an LLM-powered chatbot. \n",
    "This chatbot will be able to have a conversation and remember previous interactions.\n",
    "\n",
    "\n",
    "Note that this chatbot that we build will only use the language model to have a conversation.\n",
    "There are several other related concepts that you may be looking for:\n",
    "\n",
    "- [Conversational RAG](/docs/tutorials/qa_chat_history): Enable a chatbot experience over an external source of data\n",
    "- [Agents](https://langchain-ai.github.io/langgraphjs/tutorials/multi_agent/agent_supervisor/): Build a chatbot that can take actions\n",
    "\n",
    "This tutorial will cover the basics which will be helpful for those two more advanced topics, but feel free to skip directly to there should you choose.\n",
    "\n",
    "## Setup\n",
    "\n",
    "### Jupyter Notebook\n",
    "\n",
    "This guide (and most of the other guides in the documentation) uses [Jupyter notebooks](https://jupyter.org/) and assumes the reader is as well. Jupyter notebooks are perfect for learning how to work with LLM systems because oftentimes things can go wrong (unexpected output, API down, etc) and going through guides in an interactive environment is a great way to better understand them.\n",
    "\n",
    "This and other tutorials are perhaps most conveniently run in a Jupyter notebook. See [here](https://jupyter.org/install) for instructions on how to install.\n",
    "\n",
    "### Installation\n",
    "\n",
    "For this tutorial we will need `@langchain/core` and `langgraph`:\n",
    "\n",
    "```{=mdx}\n",
    "import Npm2Yarn from \"@theme/Npm2Yarn\"\n",
    "\n",
    "<Npm2Yarn>\n",
    "  @langchain/core @langchain/langgraph uuid\n",
    "</Npm2Yarn>\n",
    "```\n",
    "\n",
    "For more details, see our [Installation guide](/docs/how_to/installation).\n",
    "\n",
    "### LangSmith\n",
    "\n",
    "Many of the applications you build with LangChain will contain multiple steps with multiple invocations of LLM calls.\n",
    "As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your chain or agent.\n",
    "The best way to do this is with [LangSmith](https://smith.langchain.com).\n",
    "\n",
    "After you sign up at the link above, make sure to set your environment variables to start logging traces:\n",
    "\n",
    "```typescript\n",
    "process.env.LANGSMITH_TRACING = \"true\"\n",
    "process.env.LANGSMITH_API_KEY = \"...\"\n",
    "```\n",
    "\n",
    "## Quickstart\n",
    "\n",
    "First up, let's learn how to use a language model by itself. LangChain supports many different language models that you can use interchangeably - select the one you want to use below!\n",
    "\n",
    "```{=mdx}\n",
    "import ChatModelTabs from \"@theme/ChatModelTabs\";\n",
    "\n",
    "<ChatModelTabs customVarName=\"llm\" />\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "// @lc-docs-hide-cell\n",
    "\n",
    "import { ChatOpenAI } from \"@langchain/openai\";\n",
    "\n",
    "const llm = new ChatOpenAI({ model: \"gpt-4o-mini\" })"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's first use the model directly. `ChatModel`s are instances of LangChain \"Runnables\", which means they expose a standard interface for interacting with them. To just simply call the model, we can pass in a list of messages to the `.invoke` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekDrrCyaBauLYHuVv3dkacxW2G1J\",\n",
      "  \"content\": \"Hi Bob! How can I help you today?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 10,\n",
      "      \"completionTokens\": 10,\n",
      "      \"totalTokens\": 20\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 10,\n",
      "      \"completion_tokens\": 10,\n",
      "      \"total_tokens\": 20,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 10,\n",
      "    \"input_tokens\": 10,\n",
      "    \"total_tokens\": 20,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "await llm.invoke([{ role: \"user\", content: \"Hi im bob\" }])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The model on its own does not have any concept of state. For example, if you ask a followup question:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekDuOk1LjOdBVLtuCvuHjAs5aoad\",\n",
      "  \"content\": \"I'm sorry, but I don't have access to personal information about users unless you've shared it with me in this conversation. How can I assist you today?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 10,\n",
      "      \"completionTokens\": 30,\n",
      "      \"totalTokens\": 40\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 10,\n",
      "      \"completion_tokens\": 30,\n",
      "      \"total_tokens\": 40,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 30,\n",
      "    \"input_tokens\": 10,\n",
      "    \"total_tokens\": 40,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "await llm.invoke([{ role: \"user\", content: \"Whats my name\" }])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take a look at the example [LangSmith trace](https://smith.langchain.com/public/3b768e44-a319-453a-bd6e-30f9df75f16a/r)\n",
    "\n",
    "We can see that it doesn't take the previous conversation turn into context, and cannot answer the question.\n",
    "This makes for a terrible chatbot experience!\n",
    "\n",
    "To get around this, we need to pass the entire conversation history into the model. Let's see what happens when we do that:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekDyJdj6y9IREyNIf3tkKGRKhN1Z\",\n",
      "  \"content\": \"Your name is Bob! How can I help you today, Bob?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 33,\n",
      "      \"completionTokens\": 14,\n",
      "      \"totalTokens\": 47\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 33,\n",
      "      \"completion_tokens\": 14,\n",
      "      \"total_tokens\": 47,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 14,\n",
      "    \"input_tokens\": 33,\n",
      "    \"total_tokens\": 47,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "await llm.invoke([\n",
    "  { role: \"user\", content: \"Hi! I'm Bob\" },\n",
    "  { role: \"assistant\", content: \"Hello Bob! How can I assist you today?\" },\n",
    "  { role: \"user\", content: \"What's my name?\" }\n",
    "]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we can see that we get a good response!\n",
    "\n",
    "This is the basic idea underpinning a chatbot's ability to interact conversationally.\n",
    "So how do we best implement this?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Message persistence\n",
    "\n",
    "[LangGraph](https://langchain-ai.github.io/langgraphjs/) implements a built-in persistence layer, making it ideal for chat applications that support multiple conversational turns.\n",
    "\n",
    "Wrapping our chat model in a minimal LangGraph application allows us to automatically persist the message history, simplifying the development of multi-turn applications.\n",
    "\n",
    "LangGraph comes with a simple in-memory checkpointer, which we use below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { START, END, MessagesAnnotation, StateGraph, MemorySaver } from \"@langchain/langgraph\";\n",
    "\n",
    "// Define the function that calls the model\n",
    "const callModel = async (state: typeof MessagesAnnotation.State) => {\n",
    "  const response = await llm.invoke(state.messages);\n",
    "  return { messages: response };\n",
    "};\n",
    "\n",
    "// Define a new graph\n",
    "const workflow = new StateGraph(MessagesAnnotation)\n",
    "  // Define the node and edge\n",
    "  .addNode(\"model\", callModel)\n",
    "  .addEdge(START, \"model\")\n",
    "  .addEdge(\"model\", END);\n",
    "\n",
    "// Add memory\n",
    "const memory = new MemorySaver();\n",
    "const app = workflow.compile({ checkpointer: memory });"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We now need to create a `config` that we pass into the runnable every time. This config contains information that is not part of the input directly, but is still useful. In this case, we want to include a `thread_id`. This should look like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { v4 as uuidv4 } from \"uuid\";\n",
    "\n",
    "const config = { configurable: { thread_id: uuidv4() } };"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This enables us to support multiple conversation threads with a single application, a common requirement when your application has multiple users.\n",
    "\n",
    "We can then invoke the application:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekEFPclmrO7YfAe7J0zUAanS4ifx\",\n",
      "  \"content\": \"Hi Bob! How can I assist you today?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 12,\n",
      "      \"completionTokens\": 10,\n",
      "      \"totalTokens\": 22\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 12,\n",
      "      \"completion_tokens\": 10,\n",
      "      \"total_tokens\": 22,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 10,\n",
      "    \"input_tokens\": 12,\n",
      "    \"total_tokens\": 22,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const input = [\n",
    "  {\n",
    "    role: \"user\",\n",
    "    content: \"Hi! I'm Bob.\",\n",
    "  }\n",
    "]\n",
    "const output = await app.invoke({ messages: input }, config)\n",
    "// The output contains all messages in the state.\n",
    "// This will log the last message in the conversation.\n",
    "console.log(output.messages[output.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekEJgCfLodGCcuLgLQdJevH7CpCJ\",\n",
      "  \"content\": \"Your name is Bob! How can I help you today, Bob?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 34,\n",
      "      \"completionTokens\": 14,\n",
      "      \"totalTokens\": 48\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 34,\n",
      "      \"completion_tokens\": 14,\n",
      "      \"total_tokens\": 48,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 14,\n",
      "    \"input_tokens\": 34,\n",
      "    \"total_tokens\": 48,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const input2 = [\n",
    "  {\n",
    "    role: \"user\",\n",
    "    content: \"What's my name?\",\n",
    "  }\n",
    "]\n",
    "const output2 = await app.invoke({ messages: input2 }, config)\n",
    "console.log(output2.messages[output2.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great! Our chatbot now remembers things about us. If we change the config to reference a different `thread_id`, we can see that it starts the conversation fresh."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekELvPXLtjOKgLN63mQzZwvyo12J\",\n",
      "  \"content\": \"I'm sorry, but I don't have access to personal information about individuals unless you share it with me. How can I assist you today?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 11,\n",
      "      \"completionTokens\": 27,\n",
      "      \"totalTokens\": 38\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 11,\n",
      "      \"completion_tokens\": 27,\n",
      "      \"total_tokens\": 38,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_39a40c96a0\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 27,\n",
      "    \"input_tokens\": 11,\n",
      "    \"total_tokens\": 38,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config2 = { configurable: { thread_id: uuidv4() } }\n",
    "const input3 = [\n",
    "  {\n",
    "    role: \"user\",\n",
    "    content: \"What's my name?\",\n",
    "  }\n",
    "]\n",
    "const output3 = await app.invoke({ messages: input3 }, config2)\n",
    "console.log(output3.messages[output3.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, we can always go back to the original conversation (since we are persisting it in a database)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekEQ8Z5JmYquSfzPsCWv1BDTKZSh\",\n",
      "  \"content\": \"Your name is Bob. Is there something specific you would like to talk about?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 60,\n",
      "      \"completionTokens\": 16,\n",
      "      \"totalTokens\": 76\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 60,\n",
      "      \"completion_tokens\": 16,\n",
      "      \"total_tokens\": 76,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_39a40c96a0\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 16,\n",
      "    \"input_tokens\": 60,\n",
      "    \"total_tokens\": 76,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const output4 = await app.invoke({ messages: input2 }, config)\n",
    "console.log(output4.messages[output4.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is how we can support a chatbot having conversations with many users!\n",
    "\n",
    "Right now, all we've done is add a simple persistence layer around the model. We can start to make the more complicated and personalized by adding in a prompt template.\n",
    "\n",
    "## Prompt templates\n",
    "\n",
    "Prompt Templates help to turn raw user information into a format that the LLM can work with. In this case, the raw user input is just a message, which we are passing to the LLM. Let's now make that a bit more complicated. First, let's add in a system message with some custom instructions (but still taking messages as input). Next, we'll add in more input besides just the messages.\n",
    "\n",
    "To add in a system message, we will create a `ChatPromptTemplate`. We will utilize `MessagesPlaceholder` to pass all the messages in."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n",
    "\n",
    "const promptTemplate = ChatPromptTemplate.fromMessages([\n",
    "  [\"system\", \"You talk like a pirate. Answer all questions to the best of your ability.\"],\n",
    "  [\"placeholder\", \"{messages}\"],\n",
    "]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now update our application to incorporate this template:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { START, END, MessagesAnnotation, StateGraph, MemorySaver } from \"@langchain/langgraph\";\n",
    "\n",
    "// Define the function that calls the model\n",
    "const callModel2 = async (state: typeof MessagesAnnotation.State) => {\n",
    "  // highlight-start\n",
    "  const prompt = await promptTemplate.invoke(state)\n",
    "  const response = await llm.invoke(prompt);\n",
    "  // highlight-end\n",
    "  // Update message history with response:\n",
    "  return { messages: [response] };\n",
    "};\n",
    "\n",
    "// Define a new graph\n",
    "const workflow2 = new StateGraph(MessagesAnnotation)\n",
    "  // Define the (single) node in the graph\n",
    "  .addNode(\"model\", callModel2)\n",
    "  .addEdge(START, \"model\")\n",
    "  .addEdge(\"model\", END);\n",
    "\n",
    "// Add memory\n",
    "const app2 = workflow2.compile({ checkpointer: new MemorySaver() });"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We invoke the application in the same way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekEYAQVqh9OFZRGdzGiPz33WPf1v\",\n",
      "  \"content\": \"Ahoy, Jim! A pleasure to meet ye, matey! What be on yer mind this fine day?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 32,\n",
      "      \"completionTokens\": 23,\n",
      "      \"totalTokens\": 55\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 32,\n",
      "      \"completion_tokens\": 23,\n",
      "      \"total_tokens\": 55,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_39a40c96a0\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 23,\n",
      "    \"input_tokens\": 32,\n",
      "    \"total_tokens\": 55,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config3 = { configurable: { thread_id: uuidv4() } }\n",
    "const input4 = [\n",
    "  {\n",
    "    role: \"user\",\n",
    "    content: \"Hi! I'm Jim.\",\n",
    "  }\n",
    "]\n",
    "const output5 = await app2.invoke({ messages: input4 }, config3)\n",
    "console.log(output5.messages[output5.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekEbrpFI3K8BxemHZ5fG4xF2tT8x\",\n",
      "  \"content\": \"Ye be callin' yerself Jim, if I heard ye right, savvy? What else can I do fer ye, me hearty?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 68,\n",
      "      \"completionTokens\": 29,\n",
      "      \"totalTokens\": 97\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 68,\n",
      "      \"completion_tokens\": 29,\n",
      "      \"total_tokens\": 97,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 29,\n",
      "    \"input_tokens\": 68,\n",
      "    \"total_tokens\": 97,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const input5 = [\n",
    "  {\n",
    "    role: \"user\",\n",
    "    content:  \"What is my name?\"\n",
    "  }\n",
    "]\n",
    "const output6 = await app2.invoke({ messages: input5 }, config3)\n",
    "console.log(output6.messages[output6.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Awesome! Let's now make our prompt a little bit more complicated. Let's assume that the prompt template now looks something like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "const promptTemplate2 = ChatPromptTemplate.fromMessages([\n",
    "  [\"system\", \"You are a helpful assistant. Answer all questions to the best of your ability in {language}.\"],\n",
    "  [\"placeholder\", \"{messages}\"],\n",
    "]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that we have added a new `language` input to the prompt. Our application now has two parameters-- the input `messages` and `language`. We should update our application's state to reflect this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "import { START, END, StateGraph, MemorySaver, MessagesAnnotation, Annotation } from \"@langchain/langgraph\";\n",
    "\n",
    "// Define the State\n",
    "const GraphAnnotation = Annotation.Root({\n",
    "  ...MessagesAnnotation.spec,\n",
    "  language: Annotation<string>(),\n",
    "});\n",
    "\n",
    "// Define the function that calls the model\n",
    "const callModel3 = async (state: typeof GraphAnnotation.State) => {\n",
    "  const prompt = await promptTemplate2.invoke(state);\n",
    "  const response = await llm.invoke(prompt);\n",
    "  return { messages: [response] };\n",
    "};\n",
    "\n",
    "const workflow3 = new StateGraph(GraphAnnotation)\n",
    "  .addNode(\"model\", callModel3)\n",
    "  .addEdge(START, \"model\")\n",
    "  .addEdge(\"model\", END);\n",
    "\n",
    "const app3 = workflow3.compile({ checkpointer: new MemorySaver() });"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekF4R7ioefFo6PmOYo3YuCbGpROq\",\n",
      "  \"content\": \"¡Hola, Bob! ¿Cómo puedo ayudarte hoy?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 32,\n",
      "      \"completionTokens\": 11,\n",
      "      \"totalTokens\": 43\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 32,\n",
      "      \"completion_tokens\": 11,\n",
      "      \"total_tokens\": 43,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_39a40c96a0\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 11,\n",
      "    \"input_tokens\": 32,\n",
      "    \"total_tokens\": 43,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config4 = { configurable: { thread_id: uuidv4() } }\n",
    "const input6 = {\n",
    "  messages: [\n",
    "    {\n",
    "      role: \"user\",\n",
    "      content:  \"Hi im bob\"\n",
    "    }\n",
    "  ],\n",
    "  language: \"Spanish\"\n",
    "}\n",
    "const output7 = await app3.invoke(input6, config4)\n",
    "console.log(output7.messages[output7.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the entire state is persisted, so we can omit parameters like `language` if no changes are desired:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekF8yN7H81ITccWlBzSahmduP69T\",\n",
      "  \"content\": \"Tu nombre es Bob. ¿En qué puedo ayudarte, Bob?\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 56,\n",
      "      \"completionTokens\": 13,\n",
      "      \"totalTokens\": 69\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 56,\n",
      "      \"completion_tokens\": 13,\n",
      "      \"total_tokens\": 69,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 13,\n",
      "    \"input_tokens\": 56,\n",
      "    \"total_tokens\": 69,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const input7 = {\n",
    "  messages: [\n",
    "    {\n",
    "      role: \"user\",\n",
    "      content:  \"What is my name?\"\n",
    "    }\n",
    "  ],\n",
    "}\n",
    "const output8 = await app3.invoke(input7, config4)\n",
    "console.log(output8.messages[output8.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To help you understand what's happening internally, check out [this LangSmith trace](https://smith.langchain.com/public/d61630b7-6a52-4dc9-974c-8452008c498a/r)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Managing Conversation History\n",
    "\n",
    "One important concept to understand when building chatbots is how to manage conversation history. If left unmanaged, the list of messages will grow unbounded and potentially overflow the context window of the LLM. Therefore, it is important to add a step that limits the size of the messages you are passing in.\n",
    "\n",
    "**Importantly, you will want to do this BEFORE the prompt template but AFTER you load previous messages from Message History.**\n",
    "\n",
    "We can do this by adding a simple step in front of the prompt that modifies the `messages` key appropriately, and then wrap that new chain in the Message History class. \n",
    "\n",
    "LangChain comes with a few built-in helpers for [managing a list of messages](/docs/how_to/#messages). In this case we'll use the [trimMessages](/docs/how_to/trim_messages/) helper to reduce how many messages we're sending to the model. The trimmer allows us to specify how many tokens we want to keep, along with other parameters like if we want to always keep the system message and whether to allow partial messages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[\n",
      "  SystemMessage {\n",
      "    \"content\": \"you're a good assistant\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {}\n",
      "  },\n",
      "  HumanMessage {\n",
      "    \"content\": \"I like vanilla ice cream\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {}\n",
      "  },\n",
      "  AIMessage {\n",
      "    \"content\": \"nice\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {},\n",
      "    \"tool_calls\": [],\n",
      "    \"invalid_tool_calls\": []\n",
      "  },\n",
      "  HumanMessage {\n",
      "    \"content\": \"whats 2 + 2\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {}\n",
      "  },\n",
      "  AIMessage {\n",
      "    \"content\": \"4\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {},\n",
      "    \"tool_calls\": [],\n",
      "    \"invalid_tool_calls\": []\n",
      "  },\n",
      "  HumanMessage {\n",
      "    \"content\": \"thanks\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {}\n",
      "  },\n",
      "  AIMessage {\n",
      "    \"content\": \"no problem!\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {},\n",
      "    \"tool_calls\": [],\n",
      "    \"invalid_tool_calls\": []\n",
      "  },\n",
      "  HumanMessage {\n",
      "    \"content\": \"having fun?\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {}\n",
      "  },\n",
      "  AIMessage {\n",
      "    \"content\": \"yes!\",\n",
      "    \"additional_kwargs\": {},\n",
      "    \"response_metadata\": {},\n",
      "    \"tool_calls\": [],\n",
      "    \"invalid_tool_calls\": []\n",
      "  }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "import { SystemMessage, HumanMessage, AIMessage, trimMessages } from \"@langchain/core/messages\"\n",
    "\n",
    "const trimmer = trimMessages({\n",
    "  maxTokens: 10,\n",
    "  strategy: \"last\",\n",
    "  tokenCounter: (msgs) => msgs.length,\n",
    "  includeSystem: true,\n",
    "  allowPartial: false,\n",
    "  startOn: \"human\",\n",
    "})\n",
    "\n",
    "const messages = [\n",
    "    new SystemMessage(\"you're a good assistant\"),\n",
    "    new HumanMessage(\"hi! I'm bob\"),\n",
    "    new AIMessage(\"hi!\"),\n",
    "    new HumanMessage(\"I like vanilla ice cream\"),\n",
    "    new AIMessage(\"nice\"),\n",
    "    new HumanMessage(\"whats 2 + 2\"),\n",
    "    new AIMessage(\"4\"),\n",
    "    new HumanMessage(\"thanks\"),\n",
    "    new AIMessage(\"no problem!\"),\n",
    "    new HumanMessage(\"having fun?\"),\n",
    "    new AIMessage(\"yes!\"),\n",
    "]\n",
    "\n",
    "await trimmer.invoke(messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To  use it in our chain, we just need to run the trimmer before we pass the `messages` input to our prompt. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "const callModel4 = async (state: typeof GraphAnnotation.State) => {\n",
    "  // highlight-start\n",
    "  const trimmedMessage = await trimmer.invoke(state.messages);\n",
    "  const prompt = await promptTemplate2.invoke({ messages: trimmedMessage, language: state.language });\n",
    "  const response = await llm.invoke(prompt);\n",
    "  // highlight-end\n",
    "  return { messages: [response] };\n",
    "};\n",
    "\n",
    "\n",
    "const workflow4 = new StateGraph(GraphAnnotation)\n",
    "  .addNode(\"model\", callModel4)\n",
    "  .addEdge(START, \"model\")\n",
    "  .addEdge(\"model\", END);\n",
    "\n",
    "const app4 = workflow4.compile({ checkpointer: new MemorySaver() });"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now if we try asking the model our name, it won't know it since we trimmed that part of the chat history:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekHyVN7f0Pnuyc2RHVL8CxKmFfMQ\",\n",
      "  \"content\": \"I don't know your name. You haven't shared it yet!\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 97,\n",
      "      \"completionTokens\": 12,\n",
      "      \"totalTokens\": 109\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 97,\n",
      "      \"completion_tokens\": 12,\n",
      "      \"total_tokens\": 109,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 12,\n",
      "    \"input_tokens\": 97,\n",
      "    \"total_tokens\": 109,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config5 = { configurable: { thread_id: uuidv4() }}\n",
    "const input8 = {\n",
    "  // highlight-next-line\n",
    "  messages: [...messages, new HumanMessage(\"What is my name?\")],\n",
    "  language: \"English\"\n",
    "}\n",
    "\n",
    "const output9 = await app4.invoke(\n",
    "  input8,\n",
    "  config5,\n",
    ")\n",
    "console.log(output9.messages[output9.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But if we ask about information that is within the last few messages, it remembers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"chatcmpl-AekI1jwlErzHuZ3BhAxr97Ct818Pp\",\n",
      "  \"content\": \"You asked what 2 + 2 equals.\",\n",
      "  \"additional_kwargs\": {},\n",
      "  \"response_metadata\": {\n",
      "    \"tokenUsage\": {\n",
      "      \"promptTokens\": 99,\n",
      "      \"completionTokens\": 10,\n",
      "      \"totalTokens\": 109\n",
      "    },\n",
      "    \"finish_reason\": \"stop\",\n",
      "    \"usage\": {\n",
      "      \"prompt_tokens\": 99,\n",
      "      \"completion_tokens\": 10,\n",
      "      \"total_tokens\": 109,\n",
      "      \"prompt_tokens_details\": {\n",
      "        \"cached_tokens\": 0,\n",
      "        \"audio_tokens\": 0\n",
      "      },\n",
      "      \"completion_tokens_details\": {\n",
      "        \"reasoning_tokens\": 0,\n",
      "        \"audio_tokens\": 0,\n",
      "        \"accepted_prediction_tokens\": 0,\n",
      "        \"rejected_prediction_tokens\": 0\n",
      "      }\n",
      "    },\n",
      "    \"system_fingerprint\": \"fp_6fc10e10eb\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"output_tokens\": 10,\n",
      "    \"input_tokens\": 99,\n",
      "    \"total_tokens\": 109,\n",
      "    \"input_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"cache_read\": 0\n",
      "    },\n",
      "    \"output_token_details\": {\n",
      "      \"audio\": 0,\n",
      "      \"reasoning\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config6 = { configurable: { thread_id: uuidv4() }}\n",
    "const input9 = {\n",
    "  // highlight-next-line\n",
    "  messages: [...messages, new HumanMessage(\"What math problem did I ask?\")],\n",
    "  language: \"English\"\n",
    "}\n",
    "\n",
    "const output10 = await app4.invoke(\n",
    "  input9,\n",
    "  config6,\n",
    ")\n",
    "console.log(output10.messages[output10.messages.length - 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you take a look at LangSmith, you can see exactly what is happening under the hood in the [LangSmith trace](https://smith.langchain.com/public/ac63745d-8429-4ae5-8c11-9ec79d9632f2/r)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next Steps\n",
    "\n",
    "Now that you understand the basics of how to create a chatbot in LangChain, some more advanced tutorials you may be interested in are:\n",
    "\n",
    "- [Conversational RAG](/docs/tutorials/qa_chat_history): Enable a chatbot experience over an external source of data\n",
    "- [Agents](https://langchain-ai.github.io/langgraphjs/tutorials/multi_agent/agent_supervisor/): Build a chatbot that can take actions\n",
    "\n",
    "If you want to dive deeper on specifics, some things worth checking out are:\n",
    "\n",
    "- [Streaming](/docs/how_to/streaming): streaming is *crucial* for chat applications\n",
    "- [How to add message history](/docs/how_to/message_history): for a deeper dive into all things related to message history\n",
    "- [How to manage large message history](/docs/how_to/trim_messages/): more techniques for managing a large chat history\n",
    "- [LangGraph main docs](https://langchain-ai.github.io/langgraphjs/): for more detail on building with LangGraph"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TypeScript",
   "language": "typescript",
   "name": "tslab"
  },
  "language_info": {
   "codemirror_mode": {
    "mode": "typescript",
    "name": "javascript",
    "typescript": true
   },
   "file_extension": ".ts",
   "mimetype": "text/typescript",
   "name": "typescript",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
